<!-- Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -->
<!DOCTYPE html>
<title>Unit Test of e2e.CipherMode.CTR</title>
<script src="../../../../../javascript/closure/base.js"></script>
<script src="test_js_deps-runfiles.js"></script>
<script>
  goog.require('e2e.cipher.Aes');
  goog.require('e2e.ciphermode.Ctr');
  goog.require('e2e.error.UnsupportedError');
  goog.require('goog.array');
  goog.require('goog.testing.jsunit');
</script>
<script>
  function testOverflow() {
    var cipher = {
      blockSize: 1,
      encrypt: e2e.async.Result.toResult
    }
    var iv = goog.array.repeat(0x77, cipher.blockSize);
    
    // Simple overflow should pass.
    var ctr = new e2e.ciphermode.Ctr(cipher);
    var plaintext = goog.array.repeat(0x22, Math.pow(256, cipher.blockSize) - 1);
    ctr.encrypt(plaintext, iv);

    // Cycle should fail.
    var ctr = new e2e.ciphermode.Ctr(cipher);
    var plaintext = goog.array.repeat(0x22, Math.pow(256, cipher.blockSize));

    var err = assertThrows(ctr.encrypt.bind(ctr, plaintext, iv));
    assertTrue(err instanceof e2e.error.UnsupportedError);
  }

  function testConsistency() {
    var aes = new e2e.cipher.Aes(e2e.cipher.Algorithm.AES128,
                                         { key:goog.array.repeat(0x77, 16) });
    var aesctr = new e2e.ciphermode.Ctr(aes);
    var iv = goog.array.repeat(0x77, aes.blockSize);
    var plaintext = goog.array.repeat(0x22, 75);
    var ciphertext = e2e.async.Result.getValue(aesctr.encrypt(plaintext, iv));
    var deciphered =
        e2e.async.Result.getValue(aesctr.decrypt(ciphertext, iv));
    assertArrayEquals("Consistency on encryption/decryption.",
                      plaintext,
                      deciphered);
  }

  function testSmallPlaintext() {
    var aes = new e2e.cipher.Aes(e2e.cipher.Algorithm.AES128,
                                         { key:goog.array.repeat(0x77, 16) });
    var aesctr = new e2e.ciphermode.Ctr(aes);
    var iv = goog.array.repeat(0x77, aes.blockSize);
    var plaintext = goog.array.repeat(0x22, 1);
    var ciphertext = e2e.async.Result.getValue(aesctr.encrypt(plaintext, iv));
    var deciphered =
        e2e.async.Result.getValue(aesctr.decrypt(ciphertext, iv));
    assertArrayEquals("Consistency on encryption/decryption.",
                      plaintext,
                      deciphered);
  }

  function testTestVectors() {
    // NIST SP 800-38A test vectors
    var aes = new e2e.cipher.Aes(
        e2e.cipher.Algorithm.AES128,
        {
          key: [
            0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
            0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
          ]
        });
    var aesctr = new e2e.ciphermode.Ctr(aes);
    var iv = [
        0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
        0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
    ];
    var testvector = [
        0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
        0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
        0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c,
        0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51,
        0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11,
        0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef,
        0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17,
        0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10
    ];

    var expectedCipherText = [
        0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
        0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce,
        0x98, 0x06, 0xf6, 0x6b, 0x79, 0x70, 0xfd, 0xff,
        0x86, 0x17, 0x18, 0x7b, 0xb9, 0xff, 0xfd, 0xff,
        0x5a, 0xe4, 0xdf, 0x3e, 0xdb, 0xd5, 0xd3, 0x5e,
        0x5b, 0x4f, 0x09, 0x02, 0x0d, 0xb0, 0x3e, 0xab,
        0x1e, 0x03, 0x1d, 0xda, 0x2f, 0xbe, 0x03, 0xd1,
        0x79, 0x21, 0x70, 0xa0, 0xf3, 0x00, 0x9c, 0xee
    ];
    var cipherText = e2e.async.Result.getValue(aesctr.encrypt(testvector, iv))
    assertArrayEquals(expectedCipherText, cipherText);
    var plainText = e2e.async.Result.getValue(aesctr.decrypt(cipherText, iv))
    assertArrayEquals(testvector, plainText);


    aes = new e2e.cipher.Aes(e2e.cipher.Algorithm.AES192, {
      key: goog.crypt.hexToByteArray(
          '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b')
    });
    aesctr = new e2e.ciphermode.Ctr(aes);

    expectedCipherText = goog.crypt.hexToByteArray('1abc932417521ca24f2b0459f' +
        'e7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620a' +
        'bf74f78a7f6d29809585a97daec58c6b050');

    cipherText = e2e.async.Result.getValue(aesctr.encrypt(testvector, iv))
    assertArrayEquals(expectedCipherText, cipherText);
    plainText = e2e.async.Result.getValue(aesctr.decrypt(cipherText, iv))
    assertArrayEquals(testvector, plainText);


    aes = new e2e.cipher.Aes(e2e.cipher.Algorithm.AES256, {
      key: goog.crypt.hexToByteArray(
          '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4')
    });
    aesctr = new e2e.ciphermode.Ctr(aes);

    expectedCipherText = goog.crypt.hexToByteArray('601ec313775789a5b7a7f504b' +
        'bf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d849' +
        '88ddfc9c58db67aada613c2dd08457941a6');

    cipherText = e2e.async.Result.getValue(aesctr.encrypt(testvector, iv))
    assertArrayEquals(expectedCipherText, cipherText);
    plainText = e2e.async.Result.getValue(aesctr.decrypt(cipherText, iv))
    assertArrayEquals(testvector, plainText);
  }
</script>
